/*
 * Copyright 2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cc.siyecao.mapper.lambda;

import cc.siyecao.mapper.provider.entity.EntityTable;
import cc.siyecao.mapper.provider.sql.SqlScript;
import org.apache.ibatis.builder.annotation.ProviderContext;

import java.util.stream.Collectors;

/**
 * 基础的增删改查操作
 *
 * @author liuzh
 */
public class LambdaProvider {

    private static final String WRAPPER_WHERE_CLAUSE_INNER_WHEN =
            "            <when test=\"criterion.noValue\">\n" +
                    "              AND ${criterion.condition}\n" +
                    "            </when>\n" +
                    "            <when test=\"criterion.singleValue\">\n" +
                    "              AND ${criterion.condition} #{criterion.value}\n" +
                    "            </when>\n" +
                    "            <when test=\"criterion.betweenValue\">\n" +
                    "              AND ${criterion.condition} #{criterion.value} AND #{criterion.secondValue}\n" +
                    "            </when>\n" +
                    "            <when test=\"criterion.listValue\">\n" +
                    "              AND ${criterion.condition}\n" +
                    "              <foreach close=\")\" collection=\"criterion.value\" item=\"listItem\" open=\"(\" separator=\",\">\n" +
                    "                #{listItem}" +
                    "              </foreach>\n" +
                    "            </when>\n";

    public static final String WHERE_CLAUSE_CHOOSE = "      <choose>\n" +
            "      <when test=\"criteria.type == 'normal' || criteria.type == 'and'\">\n" +
            "	        AND" +
            "  	        <trim prefix=\"(\" prefixOverrides=\"AND\" suffix=\")\">\n" +
            "	                <foreach collection=\"criteria.criterionList\" item=\"criterion\">\n" +
            "	    	        <choose>\n" +
            WRAPPER_WHERE_CLAUSE_INNER_WHEN +
            "	    	        </choose>\n" +
            "                   </foreach>\n" +
            "	        </trim>\n" +
            "	    </when>\n" +
            "       <when test=\"criteria.type == 'or'\">\n" +
            "	        OR" +
            "  	        <trim prefix=\"(\" prefixOverrides=\"AND\" suffix=\")\">\n" +
            "	                <foreach collection=\"criteria.criterionList\" item=\"criterion\">\n" +
            "	    	        <choose>\n" +
            WRAPPER_WHERE_CLAUSE_INNER_WHEN +
            "	    	        </choose>\n" +
            "                   </foreach>\n" +
            "	        </trim>\n" +
            "	    </when>\n" +
            "	    </choose>\n";
    /**
     * wrapper 结构的动态 SQL 查询条件，用于接口参数只有一个 AbstractWrapper 对象时
     */
    public static final String WHERE_CLAUSE = "<where>\n" +
            "  <foreach collection=\"criterias\" item=\"criteria\" separator=\" \">\n" +
            "    <if test=\"criteria.valid\">\n" +
            WHERE_CLAUSE_CHOOSE +
            "    </if>\n" +
            "  </foreach>\n" +
            "</where>\n";

    /**
     * wrapper 结构的动态 SQL 查询条件，用于多个参数时，AbstractWrapper 对应 @Param("wrapper")
     */
    public static final String WRAPPER_WHERE_CLAUSE = "<where>\n" +
            "  <foreach collection=\"wrapper.criterias\" item=\"criteria\" separator=\" \">\n" +
            "    <if test=\"criteria.valid\">\n" +
            WHERE_CLAUSE_CHOOSE +
            "    </if>\n" +
            "  </foreach>\n" +
            "</where>\n";
    /**
     * wrapper 结构的动态 SQL 查询条件，用于多个参数时，AbstractWrapper 对应 @Param("wrapper")
     */
    public static final String UPDATE_BY_WRAPPER_COLUMN =
            "  <foreach collection=\"wrapper.setCriteria.criterionList\" item=\"criterion\"\n separator=\" , \">\n" +
                    "     ${criterion.condition} #{criterion.value}\n" +
                    "  </foreach>\n";

    /**
     * 根据 AbstractWrapper 删除
     *
     * @param providerContext 上下文
     * @return cacheKey
     */
    public static String deleteByWrapper(ProviderContext providerContext) {
        return SqlScript.caching( providerContext, (entity, util) ->
                util.ifTest( "startSql != null and startSql != ''", () -> "${startSql}" )
                        + "DELETE FROM " + entity.table()
//                        + util.parameterNotNull( "AbstractWrapper cannot be null" )
                        + WHERE_CLAUSE
                        + util.ifTest( "endSql != null and endSql != ''", () -> "${endSql}" ) );
    }

    /**
     * 根据 AbstractWrapper 条件批量更新实体信息
     *
     * @param providerContext 上下文
     * @return cacheKey
     */
    public static String updateByWrapper(ProviderContext providerContext) {
        return SqlScript.caching( providerContext, new SqlScript() {
            @Override
            public String getSql(EntityTable entity) {
                return ifTest( "wrapper.startSql != null and wrapper.startSql != ''", () -> "${wrapper.startSql}" )
                        + "UPDATE " + entity.table()
                        + set( () -> choose( () -> whenTest( "wrapper.setCriteria != null and wrapper.setCriteria.criterionList.size()>0", () -> UPDATE_BY_WRAPPER_COLUMN )
                        + otherwise( () -> entity.updateColumns().stream().map( column -> column.columnEqualsProperty( "entity." ) ).collect( Collectors.joining( "," ) ) ) ) )
                        + WRAPPER_WHERE_CLAUSE
                        + ifTest( "wrapper.endSql != null and wrapper.endSql != ''", () -> "${wrapper.endSql}" );
            }
        } );
    }

    /**
     * 根据 AbstractWrapper 条件批量更新实体不为空的字段
     *
     * @param providerContext 上下文
     * @return cacheKey
     */
    public static String updateByWrapperSelective(ProviderContext providerContext) {
        return SqlScript.caching( providerContext, new SqlScript() {
            @Override
            public String getSql(EntityTable entity) {
                return ifTest( "wrapper.startSql != null and wrapper.startSql != ''", () -> "${wrapper.startSql}" )
                        + "UPDATE " + entity.table()
                        + set( () -> choose( () -> whenTest( "wrapper.setCriteria != null and wrapper.setCriteria.criterionList.size()>0", () -> UPDATE_BY_WRAPPER_COLUMN )
                        + otherwise( () -> entity.updateColumns().stream().map(
                        column -> ifTest( column.notNullTest( "entity." ),
                                () -> column.columnEqualsProperty( "entity." ) + "," ) ).collect( Collectors.joining( LF ) ) ) ) )
                        + WRAPPER_WHERE_CLAUSE
                        + ifTest( "wrapper.endSql != null and wrapper.endSql != ''", () -> "${wrapper.endSql}" );
            }
        } );
    }

    /**
     * 根据 AbstractWrapper 条件批量查询，根据 AbstractWrapper 条件查询总数，查询结果的数量由方法定义
     *
     * @param providerContext 上下文
     * @return cacheKey
     */
    public static String selectByWrapper(ProviderContext providerContext) {
        return SqlScript.caching( providerContext, new SqlScript() {
            @Override
            public String getSql(EntityTable entity) {
                return ifTest( "startSql != null and startSql != ''", () -> "${startSql}" )
                        + "SELECT "
                        + ifTest( "distinct", () -> "distinct " )
                        + ifTest( "sqlSelect != null and sqlSelect != ''", () -> "${sqlSelect}" )
                        + ifTest( "sqlSelect == null or sqlSelect == ''", () -> entity.baseColumnAsPropertyList() )
                        + " FROM " + entity.table()
                        + ifParameterNotNull( () -> WHERE_CLAUSE )
                        + ifTest( "orderByClause != null", () -> " ORDER BY ${orderByClause}" )
                        + ifTest( "orderByClause == null", () -> entity.orderByColumn().orElse( "" ) )
                        + ifTest( "endSql != null and endSql != ''", () -> "${endSql}" );
            }
        } );
    }

    /**
     * 根据 AbstractWrapper 条件查询总数
     *
     * @param providerContext 上下文
     * @return cacheKey
     */
    public static String countByWrapper(ProviderContext providerContext) {
        return SqlScript.caching( providerContext, new SqlScript() {
            @Override
            public String getSql(EntityTable entity) {
                return ifTest( "startSql != null and startSql != ''", () -> "${startSql}" )
                        + "SELECT COUNT("
                        + ifTest( "distinct", () -> "distinct " )
                        + ifTest( "sqlSelect != null and sqlSelect != ''", () -> "${sqlSelect}" )
                        + ifTest( "sqlSelect == null or sqlSelect == ''", () -> "*" )
                        + ") FROM "
                        + entity.table()
                        + ifParameterNotNull( () -> WHERE_CLAUSE )
                        + ifTest( "endSql != null and endSql != ''", () -> "${endSql}" );
            }
        } );
    }

}
